# THIS FILE IS A PART OF VCStudio
# PYTHON 3

##############################################################################

# Now you are probably asking yourself. Network? Render? How are these related?
# Actually it's a complicated answer. So let's walk through the idea of the
# Renderer implementation in VCStudio.

# Why do we need renderer in the first place? Doesn't blender already has one?

# Well yes. And I would not care if I was always sitting on a biffy, nice 
# machine. But back in 2016, 2017 when I was making I'm Not Even Human I realized
# one very ennoying fact.

# My lap top at the time could not manage the loads of the files. And while
# rendering Blender would crash very often. Of course I was not dumb as was
# rendering into image files and not straight into video. So I could start
# rendering from the last frame. 

# Unfortunatly it was happening way too often. So I started looking at ways to
# unload things from the memory so Blender would crash less. One of those ways
# is rendering using a console. Type blender -b <filename> -a and it will launch
# blender without UI saving some memory. Also I could do a simple script that
# restores Blender when if it does crashes. 

# By the time of Blender-Organizer 4.0 I had a system of rendering that will
# look into a folder and see if any file between start and end frame are missing
# and render them. Instead of trying to render every single frame in sequense.

# Also in Blender-Organizer 4.0 I made a very clever thing. I openned the 
# rendering not in a terminal, but rather in it's own process that is piped to
# a little UI window. Where I show some quick analytics. Tho there was one
# problem. Closing the window was closing the blender. Or if not there was no
# way to cancel it. And it would continue till the end or till the crash.

# So here since VCStudio is a complite re-write of Blender-Organizer I can try
# to re-implement the renderer in a slightly better way.

# I will run a script that recieves stuff from the pipe and has an ability to 
# kill the blender's process. And this script will have no UI. But will instead
# use LOCAL NETWORK to talk to VCStudio. I'm using 127.0.0.1 for this so on
# any normal system even without Internet connection it should work. Since this
# IP adress simply means THIS COMPUTER. 

# I will probably implement some LOCAL NETWORK talk ability later for the 
# multiuser. 

# This file is collection of functions for Rendering network sub-system. The UI
# are contained in studio folder. Render it self is a separate file.

##############################################################################

import os
import json
import time
import socket

def get_runtime_data(project):

    # This will read a specific .json file from the system.

    template = {"to_render":True,
                "current_progress":"Fra: 69 | Mem: 420 |Sample: 69/420"}
    try:
        with open(project+"/render_runtime.json") as json_file: 
            data = json.load(json_file)
    except:
        data = template.copy()

    return data

def save_runtime_data(data, project):
    with open(project+"/render_runtime.json", 'w') as fp:
        json.dump(data, fp, indent=4)

def read_renders(win):
    
    # This function will listen for 127.0.0.1 port 54545 for data about current
    # renders. NOTE: I'm not doing any encryption and will use UDP protocol. 
    # so it's quite simple to mess arround with it. NO SECURITY. But I don't see
    # any use in hacking it appart from maybe making VCStudio think it's rendering
    # a different frame or something.
    
    # First thing. And you probably think that I'm crazy but bare with me. I
    # need to see if a active_renders.data exists. And will read from it on 
    # each frame. I know. A bit of not cool. But to be honest. The language 
    # files are also read at every frame. Yeah...
    
    if not os.path.exists(win.project+"/set/active_renders.data"):
        m = open(win.project+"/set/active_renders.data", "w")
        m.close()
    
    # Now we are going to read it on every frame to see that renders are still
    # there.
    
    r = open(win.project+"/set/active_renders.data")
    r = r.read()
    r = r.split("\n")
    
    filenames = []
    
    for filename in r:
        if filename:
            
            # So basically the file contains list of files currently placed for
            # rendering. The renderer will go one by one and when finised will
            # remove the filename from this file. Also to cancel the render the
            # filename should be removed. The rest will be done using a very
            # crappy UDP network protocol on localhost. 
            
            # Now if you just openned the VCStudio. While maybe render were
            # doing their job somewhere on the background. You want to re-new
            # all of the data.
            
            # So...
            
            if filename not in win.renders:
                
                # We are going to read the JSON file of the render.
                
                folder = filename[:filename.rfind("/")]+"/extra"
                savefile = folder+filename[filename.rfind("/"):]+".json"
                
                # It might not exits so we are going to do this with try.
                
                try:
                    with open(win.project+savefile) as json_file: 
                        data = json.load(json_file) 
                    
                    win.renders[filename] = data
                    
                except:
                    pass
                
            # Now let's make a list of all lines anyway
            
            if filename not in filenames:
                filenames.append(filename)
                
    # Now let's remove any of them renders that are finished or otherwise
    # removed from the file.
    
    for stuff in list(win.renders.keys()):
        
        if stuff not in filenames:
            del win.renders[stuff]
            try:
                if stuff == win.current["renders_window"]["filename"]:
                    win.current["renders_window"]["filename"] = ""
            except:
                pass
    
    # Now that we know about the data. Let's read the stream from the network
    
    string_read = ""
    
    # try:
    #     UDP_IP = "127.0.0.1"
    #     UDP_PORT = 54545
        
    #     sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 
    #     sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    #     sock.bind((UDP_IP, UDP_PORT))
        
    #     # Now usually it will wait for a message untill one appears. But render
    #     # could be finished. Or some error might accur. Or it could be that no
    #     # render what so ever. So I don't want the UI to freez. So I'm going to
    #     # put a timeout. Very short timeout.
        
    #     sock.settimeout(0.005)
        
    #     # This comes with it's own problems. Mainly I tested I need to send
    #     # about 500 messages at ones for it to be recognized at all. But it's
    #     # not a big deal what so ever. Just need to keep in mind. 500 messages.
        
    #     data, addr = sock.recvfrom(1024)
    #     data = data.decode('utf8')
        
    #     sock.close()
        
    #     string_read = data
        
    # except:
    #     pass

    runtime = get_runtime_data(win.project)

    if int(time.time()) > runtime.get("running", 0) + 100 or not runtime.get("to_render"):
        #print("Should be off")
        for blend in win.renders:
            win.renders[blend]["rendering"] = False

    else:
        #print("Should be on...")

        blend, blend_string = runtime.get("current_file", ""), runtime.get("current_progress", "")

        # So we've got 2 peaces of data from the renderer. Blender. Which
        # is a path to the blend file. Similar to the one in 
        # active_renders.data file that we read previously.

        # And we've got a raw string that blender outputs during render.
        # Example:
        #  Fra:70 Mem:93.09M (Peak 97.78M) | Time:00:01.83 | Rendering 26 / 64 samples

        # You can find a string like this in the top banner inside the blender.

        # From this string we can find out a bunch of things. But not everything is
        # that simple. Cycles and Eevee for exmple output different strings. Any 
        # other, wild render engine will output it's own string.

        # Now I'd like to actually load the data from the JSON file at every step
        # like this. Because our rendering script will be recording and saving
        # render times of each frame into the file. I need it for analytics.

        if blend in win.renders:

            folder = blend[:blend.rfind("/")]+"/extra"
            savefile = folder+blend[blend.rfind("/"):]+".json"

            # It might not exits so we are going to do this with try.

            try:
                with open(win.project+savefile) as json_file: 
                    data = json.load(json_file) 

                win.renders[blend] = data
            except:
                pass

            win.renders[blend]["rendering"] = blend_string

        # Next is let's try finding out the current frame rendering. It's 
        # probably not that hard. Every string usually starts with Fra:<number>

        # So let's try doing it.
        frame = 0
        try:
            frame = int(blend_string[4:blend_string.find(" ")])
        except:
            pass

        win.renders[blend]["current_frame"] = frame

        # The rest of it I will do in the UI.


            
   
            
    
def stop_render(win):
    
    # This function will stop the rendering. It will bombard the renderer with 
    # stop messages.
    
    # for i in range(5000):    
    #     cs1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    #     cs1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    #     cs1.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
    #     cs1.sendto(bytes("VCStudio : RENDER STOP", 'utf-8'), ('127.0.0.1', 54545)) 

    runtime = get_runtime_data(win.project)
    runtime["to_render"] = False
    save_runtime_data(runtime, win.project)

    for blend in win.renders:
        win.renders[blend]["rendering"] = False
    
    
    
    
